AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
AddCSLuaFile("cl_hud.lua")
AddCSLuaFile("cl_menu.lua")
AddCSLuaFile("cl_msgstack.lua")
AddCSLuaFile("player_ext_shd.lua")
 
include("shared.lua")
include("player.lua")
include("player_ext.lua")
include("player_ext_shd.lua")
include("gamemsg.lua")
include("data.lua")
include("dmginfo.lua")

CreateConVar("ze_roundtime_minutes", "10", FCVAR_NOTIFY)
CreateConVar("ze_preptime_seconds", "20", FCVAR_NOTIFY)
CreateConVar("ze_posttime_seconds", "5", FCVAR_NOTIFY)

CreateConVar("ze_zombie_pct", "0.125")
CreateConVar("ze_zombie_max", "64")

concommand.Add("team_human", team_human)
concommand.Add("team_zombie", team_zombie)
concommand.Add("class", SetClassCMD)

resource.AddFile("materials/HUD/bloodsplat.vmt")

function GM:Initialize()
	DataInitialize()

	MsgN("Zombie Escape gamemode initializing...")
	
	GAMEMODE.state = STATE_WAIT
	
	PlayerWait()
end

function SendRoundState(state, ply)
   if ply then
      umsg.Start("state", ply)
   else
      umsg.Start("state")
   end
   umsg.Char(state)
   umsg.End()
end

function SetRoundState(state)
	GAMEMODE.state = state
	
	SendRoundState(state)
end

function GetRoundState()
	return GAMEMODE.state
end

function SetRoundEnd(time)
	SetGlobalFloat("ze_round_end", time)
end

local function EnoughPlayers()
	ready = GetGlobalFloat("ze_players")
	
	return ready >= 2
end

local function StopRoundTimers()
	timer.Stop("prep")
	timer.Stop("start")
	timer.Stop("wincheck")
	timer.Stop("post")
end

function PlayerCheck()
	if GetRoundState() == STATE_WAIT then
		if EnoughPlayers() then
			timer.Create("prep", 1, 1, PrepareState)
			timer.Stop("playercheck")
		end
	end
	if not EnoughPlayers() then
		PlayerWait()
		GameMsg("Waiting for enough players")
	end
end

function PlayerWait()
	StopRoundTimers()
	SetRoundState(STATE_WAIT)
	
	-- if not timer.Start("playercheck") then
		-- timer.Create("playercheck", 1, 0, PlayerCheck)
	-- end
end

function PrepareState()
	if not EnoughPlayers() then
	return end

	SetRoundState(STATE_PREP)
	
	local time = GetConVar("ze_preptime_seconds"):GetInt()
	
	timer.Stop("prep")
	
	timer.Create("start", time, 1, StartRound)
	SetRoundEnd(CurTime() + time)
	
	GameMsg("Preparing round, get ready!")
end

function StartRound()
	if not EnoughPlayers() then
	return end
	
	SetRoundState(STATE_ACTIVE)
	
	for k,ply in pairs(player.GetAll()) do
		if IsValid(ply) then
			if not ply:Alive() then
				ply:StripWeapons()
				ply:Spawn()
				ply:SetHuman()
			elseif ply:Health() < ply:GetMaxHealth() then
				ply:SetHealth(ply:GetMaxHealth())
			end
		end
	end
	
	local time = GetConVar("ze_roundtime_minutes"):GetInt() * 60
	
	timer.Stop("start")
	
	PickZombie()
	
	StartWinCheck()
	SetRoundEnd(CurTime() + time)
	GameMsg("Started round!")
end

function StartWinCheck()
	if not timer.Start("wincheck") then
		timer.Create("wincheck", 5, 0, WinCheck)
	end
end

function StopWinCheck()
	timer.Stop("wincheck")
end

function WinCheck()
	if GetRoundState() == STATE_ACTIVE then
		if CurTime() > GetGlobalFloat("ze_round_end", 0) then
			EndRound(WIN_TIME)
		else
			local win = LiveCheck()
			
			if win != WIN_NONE then
				EndRound(win)
				return
			end
		end
	end
end

function LiveCheck()
	local human_alive = false
	local zombie_alive = false
	
	for k,v in pairs(player.GetAll()) do
		if v:Alive() then
			if v:IsHuman() then
				human_alive = true
			elseif v:IsZombie() then
				zombie_alive = true
			end
		end
		
		if human_alive and zombie_alive then
			return WIN_NONE
		end
	end
	
	if human_alive and not zombie_alive then
		return WIN_HUMAN
	elseif not human_alive and zombie_alive then
		return WIN_ZOMBIE
	elseif not human_Alive then
		return WIN_HUMAN
	end
	
	return WIN_NONE
end

function EndRound(type)
	SetRoundState(STATE_POST)
	
	ServerLog("Round ended.\n")

	if type == WIN_TIME then
		ServerLog("Result: Timelimit reached, Zombies lose.\n")
		GameMsg("Timelimit has been reached, humans win")
	elseif type == WIN_HUMAN then
		ServerLog("Result: Humans won.\n")
		GameMsg("Humans have won.")
	elseif type == WIN_ZOMBIE then
		ServerLog("Result: Zombies won.\n")
		ZombieMsg(player.GetAll(), "Zombies have taken over.")
	else
		ServerLog("Result: Unknown victory condition!\n")
	end
	GameMsg("New round starting.")

	StopWinCheck()
	
	local time = GetConVar("ze_posttime_seconds"):GetInt()
	
	timer.Stop("end")
	
	timer.Create("post", time, 1, PrepareState)
	SetRoundEnd(CurTime() + time)
	
	timer.Simple(time - 0.5, CleanUp)
end

local function GetZombieCount(ply_count)
	local zombie_count = math.floor(ply_count * GetConVar("ze_zombie_pct"):GetFloat())

	zombie_count = math.Clamp(zombie_count, 1, GetConVar("ze_zombie_max"):GetInt())

	return zombie_count
end

function PickZombie()
	local choices = { }

	for k,v in pairs(player.GetAll()) do
		if IsValid(v) then
			table.insert(choices, v)
		end
	end
	
	local count = #choices
	local zombie_count = GetZombieCount(count)
	
	if count == 0 then return end
	
	local zombies = 0
	
	while zombies < zombie_count do
		local pick = math.random(1, #choices)
		local plypick = choices[pick]
		
		if IsValid(plypick) then
			plypick:SetZombie()
			
			plypick:EmitSound("npc/fast_zombie/fz_scream1.wav", 100)
			
			table.remove(choices, pick)
			zombies = zombies + 1
		end
	end
end

function GM:PlayerCanPickupWeapon(ply, wep)
	if ply:IsHuman() or wep:GetClass() == "weapon_crowbar" then return true
	else return false
	end
end

function CleanUp()
	game.CleanUpMap()
	
	PlayerRespawn()
	
	for k,new in pairs(ents.FindByClass("point_servercommand_new")) do
		new:Remove()
	end
	for k,old in pairs(ents.FindByClass("point_servercommand")) do
		local nm = old.targetname
		local ent = ents.Create("point_servercommand_new")
		ent:SetPos(old:GetPos())
		old:Remove()
		ent:SetKeyValue("targetname", nm)
		ent:Spawn()
	end
end

function GM:EntityKeyValue(e, k, v)
    if e:GetClass() == "point_servercommand" then
        if k == "targetname" then
            e.targetname = v
        end
    end
end

function GM:InitPostEntity()
	for k,v in pairs(ents.FindByClass("point_servercommand")) do
		local nm = v.targetname
		local ent = ents.Create("point_servercommand_new")
		ent:SetPos(v:GetPos())
		v:Remove()
		ent:SetKeyValue("targetname", nm)
		ent:Spawn()
	end
end